﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.DynamicData;
using System.Web.Hosting;
using System.Web.UI.WebControls;
using System.Web;
using System.Security.Permissions;
using System.ComponentModel.DataAnnotations;
using System.Collections;
using System.Globalization;
using System.Web.Compilation;
using System.Diagnostics.CodeAnalysis;
using System.Web.Caching;
using Catalyst.ComponentModel.DataAnnotations;

namespace Catalyst.Web.DynamicData
{
    /// <summary>
    /// The factory is responsible for loading the correct FilterUserControlBase based on Type, RenderUIHint, and FilterControlMode.
    /// </summary>
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    public class FilterTemplateFactory
    {
        private Hashtable _filterTemplateVirtualPathCache;
        private bool _registeredForChangeNotifications;
        private string _templateFolderVirtualPath;
        private Dictionary<Type, Type> _typesFallBacks;
        private Dictionary<Type, string> _typesToTemplateNames;
        private VirtualPathProvider _vpp;
        private const string BooleanField = "Boolean";
        private const string ChildrenField = "Children";
        private const string ContainsModePathModifier = "_Contains";
        private const string DateTimeField = "DateTime";
        private const string DecimalField = "Decimal";
        private const string ForeignKeyField = "ForeignKey";
        private const string IntegerField = "Integer";
        private const string MultiSelectModePathModifier = "_MultiSelect";
        private const string RangeModePathModifier = "_Range";
        private const string TextField = "Text";

        public FilterTemplateFactory()
        {
            this._filterTemplateVirtualPathCache = new Hashtable();
            this.TemplateFolderVirtualPath = "~/DynamicData/FilterTemplates/";
            this.InitTypesToTemplateNamesTable();
            this.BuildTypeFallbackTable();
        }

        internal FilterTemplateFactory(VirtualPathProvider vpp)
            : this()
        {
            this._vpp = vpp;
            FileChangeNotifier.VirtualPathProvider = vpp;
        }

        private void BuildTypeFallbackTable()
        {
            this._typesFallBacks = new Dictionary<Type, Type>();
            this._typesFallBacks[typeof(float)] = typeof(decimal);
            this._typesFallBacks[typeof(double)] = typeof(decimal);
            this._typesFallBacks[typeof(short)] = typeof(int);
            this._typesFallBacks[typeof(byte)] = typeof(int);
            this._typesFallBacks[typeof(long)] = typeof(int);
            this._typesFallBacks[typeof(char)] = typeof(string);
            this._typesFallBacks[typeof(int)] = typeof(string);
            this._typesFallBacks[typeof(decimal)] = typeof(string);
            this._typesFallBacks[typeof(Guid)] = typeof(string);
        }

        public virtual string BuildVirtualPath(string templateName, MetaColumn column, FilterControlMode mode)
        {
            if (string.IsNullOrEmpty(templateName))
            {
                throw new ArgumentNullException("templateName");
            }
            string str = null;
            switch (mode)
            {
                case FilterControlMode.Contains:
                    str = ContainsModePathModifier;
                    break;
                case FilterControlMode.Range:
                    str = RangeModePathModifier;
                    break;
                case FilterControlMode.MultiSelect:
                    str = MultiSelectModePathModifier;
                    break;
            }
            return string.Format(CultureInfo.InvariantCulture, this.TemplateFolderVirtualPath + "{0}{1}.ascx", new object[] { templateName, str });
        }

        public virtual IFieldTemplate CreateFieldTemplate(MetaColumn column, FilterControlMode mode, string uiHint)
        {
            string virtualPath = this.GetFieldTemplateVirtualPathWithCaching(column, mode, uiHint);
            if (virtualPath == null)
            {
                return null;
            }
            return (IFieldTemplate)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(IFieldTemplate));
        }

        private void EnsureRegisteredForChangeNotifications()
        {
            FileChangedCallback onFileChanged = null;
            if (!this._registeredForChangeNotifications)
            {
                lock (this)
                {
                    if (!this._registeredForChangeNotifications)
                    {
                        if (!this.VirtualPathProvider.DirectoryExists(this._templateFolderVirtualPath))
                        {
                            throw new Exception(string.Format(CultureInfo.CurrentCulture, "The field template folder '{0}' does not exist", new object[] { this.TemplateFolderVirtualPath }));
                        }
                        if (onFileChanged == null)
                        {
                            onFileChanged = delegate(string path)
                            {
                                lock (this._filterTemplateVirtualPathCache)
                                {
                                    this._filterTemplateVirtualPathCache.Clear();
                                }
                            };
                        }
                        FileChangeNotifier.Register(this.TemplateFolderVirtualPath, onFileChanged);
                        this._registeredForChangeNotifications = true;
                    }
                }
            }
        }

        public virtual string GetFieldTemplateVirtualPath(MetaColumn column, FilterControlMode mode, string uiHint)
        {
            if (string.IsNullOrEmpty(uiHint))
            {
                if (column is MetaForeignKeyColumn)
                {
                    uiHint = ForeignKeyField;
                }
                else if (column is MetaChildrenColumn)
                {
                    uiHint = ChildrenField;
                }
            }
            return this.GetVirtualPathWithModeFallback(uiHint, column, mode);
        }

        internal string GetFieldTemplateVirtualPathWithCaching(MetaColumn column, FilterControlMode mode, string uiHint)
        {
            this.EnsureRegisteredForChangeNotifications();
            long key = CombineHashCodes(uiHint, column, mode);
            string str = (string)this._filterTemplateVirtualPathCache[key];
            if ((str == null) && !this._filterTemplateVirtualPathCache.ContainsKey(key))
            {
                str = this.GetFieldTemplateVirtualPath(column, mode, uiHint);
                lock (this._filterTemplateVirtualPathCache)
                {
                    this._filterTemplateVirtualPathCache[key] = str;
                }
            }
            return str;
        }

        private string GetVirtualPathForMode(string templateName, MetaColumn column, FilterControlMode mode)
        {
            if (!string.IsNullOrEmpty(templateName))
            {
                string str = this.GetVirtualPathIfExists(templateName, column, mode);
                if (str != null)
                {
                    return str;
                }
            }
            return this.GetVirtualPathForTypeWithFallback(column.ColumnType, column, mode);
        }

        private string GetVirtualPathForTypeWithFallback(Type fieldType, MetaColumn column, FilterControlMode mode)
        {
            string customDataType;
            Type type;
            DataTypeAttribute dataTypeAttribute = column.DataTypeAttribute;
            if (dataTypeAttribute != null)
            {
                if (dataTypeAttribute.DataType == DataType.Custom)
                {
                    customDataType = dataTypeAttribute.CustomDataType;
                }
                else
                {
                    customDataType = dataTypeAttribute.DataType.ToString();
                }
                string str2 = this.GetVirtualPathIfExists(customDataType, column, mode);
                if (str2 != null)
                {
                    return str2;
                }
            }
            if (this._typesToTemplateNames.TryGetValue(fieldType, out customDataType))
            {
                string str3 = this.GetVirtualPathIfExists(customDataType, column, mode);
                if (str3 != null)
                {
                    return str3;
                }
            }
            if (!this._typesFallBacks.TryGetValue(fieldType, out type))
            {
                return null;
            }
            return this.GetVirtualPathForTypeWithFallback(type, column, mode);
        }

        private string GetVirtualPathIfExists(string templateName, MetaColumn column, FilterControlMode mode)
        {
            string virtualPath = this.BuildVirtualPath(templateName, column, mode);
            if (this.VirtualPathProvider.FileExists(virtualPath))
            {
                return virtualPath;
            }
            return null;
        }

        private string GetVirtualPathWithModeFallback(string templateName, MetaColumn column, FilterControlMode mode)
        {
            string str = this.GetVirtualPathForMode(templateName, column, mode);
            if (str != null)
                return str;
            throw new Exception(string.Format(CultureInfo.CurrentCulture, "Couldn't find field template for column {0}.{1}", new object[] { column.Table.Name, column.Name }));
        }

        private void InitTypesToTemplateNamesTable()
        {
            this._typesToTemplateNames = new Dictionary<Type, string>();
            this._typesToTemplateNames[typeof(int)] = IntegerField;
            this._typesToTemplateNames[typeof(bool)] = BooleanField;
            this._typesToTemplateNames[typeof(DateTime)] = DateTimeField;
            this._typesToTemplateNames[typeof(string)] = TextField;
            this._typesToTemplateNames[typeof(decimal)] = DecimalField;
        }

        public string TemplateFolderVirtualPath
        {
            get
            {
                return this._templateFolderVirtualPath;
            }
            set
            {
                this._templateFolderVirtualPath = VirtualPathUtility.AppendTrailingSlash(value);
                this._registeredForChangeNotifications = false;
            }
        }

        private VirtualPathProvider VirtualPathProvider
        {
            get
            {
                if (this._vpp == null)
                {
                    this._vpp = HostingEnvironment.VirtualPathProvider;
                }
                return this._vpp;
            }
        }

        private static long AddHashCode(long currentHash, object o)
        {
            if (o == null)
            {
                return currentHash;
            }
            return (((currentHash << 5) + currentHash) ^ o.GetHashCode());
        }

        private static long CombineHashCodes(object o1, object o2, object o3)
        {
            long currentHash = 0x1505L;
            return AddHashCode(AddHashCode(AddHashCode(currentHash, o1), o2), o3);
        }


        private delegate void FileChangedCallback(string path);

        private class FileChangeNotifier
        {
            // Fields
            private FileChangedCallback _onFileChanged;
            private static VirtualPathProvider _vpp;

            // Methods
            private FileChangeNotifier(string virtualPath, FileChangedCallback onFileChanged)
            {
                this._onFileChanged = onFileChanged;
                this.RegisterForNextNotification(virtualPath);
            }

            private void OnCacheItemRemoved(string key, object value, CacheItemRemovedReason reason)
            {
                if (reason == CacheItemRemovedReason.DependencyChanged)
                {
                    this._onFileChanged(key);
                    this.RegisterForNextNotification(key);
                }
            }

            [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Web.DynamicData.FileChangeNotifier", Justification = "The object deals with file change notifications and we don't need to hold on to it")]
            public static void Register(string virtualPath, FileChangedCallback onFileChanged)
            {
                new FileChangeNotifier(virtualPath, onFileChanged);
            }

            private void RegisterForNextNotification(string virtualPath)
            {
                List<string> virtualPathDependencies = new List<string>();
                virtualPathDependencies.Add(virtualPath);
                CacheDependency dependencies = VirtualPathProvider.GetCacheDependency(virtualPath, virtualPathDependencies, DateTime.UtcNow);
                HttpRuntime.Cache.Insert(virtualPath, virtualPath, dependencies, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, new CacheItemRemovedCallback(this.OnCacheItemRemoved));
            }

            // Properties
            public static VirtualPathProvider VirtualPathProvider
            {
                private get
                {
                    if (_vpp == null)
                    {
                        _vpp = HostingEnvironment.VirtualPathProvider;
                    }
                    return _vpp;
                }
                set
                {
                    _vpp = value;
                }
            }
        }
    }
}
